/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.util;
 
/**
   This class contains utility classes that are used throughout the API.
   Some of the methods contained here are helpful in the marshalling and
   unmarshalling of information contained within the protocol packets. Other
   methods which include printing bytes, ints ,... as a sequence of 1's and
   0's are primarily intended for debugging purposes. Other methods include
   conversion of integer arrays into byte arrays and vice versa.
   
   @author Shrideep Pallickara
   $Revision$
   $Date$
*/

public class ByteUtilities {
  
  private byte[] addressInBytes;
  private int[] addressInInts;
  
  /** Constructor initialized using a sequence of bytes. This allows us to 
      retrieve the same sequence as a sequence of integers.
      @param addressInBytes The address as a sequence of bytes.
  */
  public ByteUtilities(byte[] addressInBytes) {
    this.addressInBytes = addressInBytes;
    addressInInts  = byteArrayToIntArray(addressInBytes);
  }
  
  /** Constructor initialized using a sequence of integers. This allows us to 
      retrieve the same sequence as a sequence of bytes. 
      @param addressInInts The address as a sequence of integers.
  */
  public ByteUtilities(int[] addressInInts) {
    this.addressInInts = addressInInts;
    addressInBytes = intArrayToByteArray(addressInInts);
  }
  
  /** This allows us to retrieve an integer sequence as a sequence of bytes. 
      @return The address as a sequence of bytes.
  */
  public byte[] getAddressInBytes() {
    return addressInBytes;
  }
  
  /** This allows us to retrieve an byte sequence as a sequence of integers. 
      @return The address as a sequence of integers.
  */
  public int[] getAddressInInts() {
    return addressInInts;
  }
  
  public byte[] intArrayToByteArray(int[] intArray) {
    int inputArrayLength = intArray.length;
    byte[] byteArray = new byte[inputArrayLength*2];
    
    for (int i=0; i< inputArrayLength; i++) {
      byteArray[2*i]    = (byte) ((intArray[i] >>> 8) & 0xFF);
      byteArray[(2*i) +1] = (byte) ((intArray[i] >>> 0) & 0xFF);
    }
    return byteArray;
  }
  
  public static byte[] intArrayToByteArray(int[] intArray, int offset, 
					   int len) {
    if ((intArray.length - offset) != len)
      throw new IllegalArgumentException("byte utility exception");
    int bytesLen = len*4;
    byte[] byteArray = new byte[bytesLen];
    for (int j=0, i=offset; j < bytesLen; i++, j=j+4) {
      
      byteArray[j]    = (byte) ((intArray[i] >>> 24) & 0xFF);
      byteArray[j+1] = (byte) ((intArray[i] >>> 16) & 0xFF);
      byteArray[j+2] = (byte) ((intArray[i] >>> 8) & 0xFF);
      byteArray[j+3] = (byte) ((intArray[i] >>> 0) & 0xFF);
      
      
      //System.arraycopy(getBytes(intArray, i), 0, byteArray, j, 4);
      
    }
    return byteArray;	
  }
  
  public int[] byteArrayToIntArray(byte[] byteArray) {
    int inputArrayLength = byteArray.length;
    /** 2 "bytes" in our node address scheme comprise an "int" in the 
	corresponding integer addressing scheme */
    if (inputArrayLength%2 !=0) {
      System.out.println("NodeAddress :: Messed up integer array address");
    }
    int[] intArray = new int[inputArrayLength/2];
    for (int i=0; i < inputArrayLength/2; i++) {
      intArray[i] = getValue(byteArray[2*i], byteArray[(2*i) + 1]);
    }
    return intArray;
  }

  public static int[] byteArrayToIntArray(byte[] byteArray, int offset, 
					  int numOfBytes){
    
    if (numOfBytes%4 !=0) {
      throw new IllegalArgumentException("byte utility exception");
    }
    int len = numOfBytes/4;
    int[] intArray = new int[len];
    for (int i=offset, j=0; i < numOfBytes; j++, i=i+4) {
      intArray[j] = getInt(byteArray, i);
      //byte[] b = new byte[4];
      //System.arraycopy(byteArray, offset, b, 0, 4);
      //intArray[j] = getInt(b);
    }
    return intArray;
  }
  
  /** Give two bytes, it returns the Integer value of the 16-bit number
      encoded within the 2 bytes a, b */
  public int getValue(byte a, byte b) {
    int x = a;
    int y = b; 
    /** The 8th-bit in the byte is 1, therefore it gets treated as as
	a -ve integer ... accomodate this case */
    if (x < 0)
      x += 256;
    if (y < 0)
      y += 256;
    return ((x << 8) + (y << 0));
  }
  
  public static int getIntFromByte(byte a) {
    int x = a ;
    if (x<0) 
      x+=256;
    return x;
  }
  
  /** Returns the bytes that are associated with a short value.
      @param value The short for which we are seeking an equivalent
      byte stream representation.
      @return The short value as a sequence of bytes 
  */
  public static byte[] getBytes(short value) {
    byte[] temp = new byte[2];
    temp[0] = (byte)((value >>> 8) & 0xFF);
    temp[1] = (byte)((value >>> 0) & 0xFF);
    return temp;
  }
  
  /** Returns the bytes that are associated with an integer value.
      @param value The integer for which we are seeking an equivalent
      byte stream representation.
      @return The int value as a sequence of bytes 
  */
  public static byte[] getBytes(int value) {
    byte[] temp = new byte[4];
    temp[0] = (byte)((value >>> 24) & 0xFF);
    temp[1] = (byte)((value >>> 16) & 0xFF);
    temp[2] = (byte)((value >>>  8) & 0xFF);
    temp[3] = (byte)((value >>>  0) & 0xFF); 
    return temp;
  }


  public static byte[] getBytes(int[] intArray, int pos) {
    if (intArray.length <= pos)
      throw new IllegalArgumentException("byte utilities exception");
    int thisInt = intArray[pos];
    return getBytes(thisInt);
  }
  


  /** Returns the bytes that are associated with a long value.
      @param value The long for which we are seeking an equivalent
      byte stream representation.
      @return The long value as a sequence of bytes 
  */
  public static byte[] getBytes(long value) {
    byte[] temp = new byte[8];
    temp[0] = (byte)((value >>> 56) & 0xFF);
    temp[1] = (byte)((value >>> 48) & 0xFF);
    temp[2] = (byte)((value >>> 40) & 0xFF);
    temp[3] = (byte)((value >>> 32) & 0xFF);
    temp[4] = (byte)((value >>> 24) & 0xFF);
    temp[5] = (byte)((value >>> 16) & 0xFF);
    temp[6] = (byte)((value >>>  8) & 0xFF);
    temp[7] = (byte)((value >>>  0) & 0xFF);
    return temp;
  }




  /** Given a sequence of bytes, this method returns the short value associated
      with the sequence of bytes. This sequence of bytes passed as the 
      argument should be of length=2.
      @param data The sequence of bytes representing a short value.
      @return The short value associated with the sequence of bytes 
  */
  public static short getShort(byte[] data) {
    int x, y;
    x =  data[0];
    y =  data[1];
    if (x < 0) x+=256;
    if (y < 0) y+=256;
    return (short)((x <<8) + (y <<0) );
  } 


  
  /** Given a sequence of bytes, this method returns the int value associated
      with the sequence of bytes. This sequence of bytes passed as the 
      argument should be of length=4.
      @param data The sequence of bytes representing a int value.
      @return The integer value associated with the sequence of bytes 
  */
  public static int getInt(byte[] data) {
    int w, x, y, z;
    w = data[0];
    x = data[1];
    y = data[2];
    z = data[3];
    if (w < 0) w+=256;
    if (x < 0) x+=256;
    if (y < 0) y+=256;
    if (z < 0) z+=256;
    return ((w << 24) + (x <<16) + (y <<8) + (z <<0));
  }



  public static int getInt(byte[] data, int offset) {
    if ((data.length - offset) < 4)
      throw new IllegalArgumentException("byte utilities exception");
    
    int w, x, y, z;
    w = data[0+offset];
    x = data[1+offset];
    y = data[2+offset];
    z = data[3+offset];
    if (w < 0) w+=256;
    if (x < 0) x+=256;
    if (y < 0) y+=256;
    if (z < 0) z+=256;
    return ((w << 24) + (x <<16) + (y <<8) + (z <<0));	
  }



  /** Given a sequence of bytes, this method returns the long value associated
      with the sequence of bytes. This sequence of bytes passed as the 
      argument should be of length=8.
      @param data The sequence of bytes representing a long value.
      @return The long value associated with the sequence of bytes 
  */
  public static long getLong(byte[] data) {
    byte[] data1 = new byte[4];
    byte[] data2 = new byte[4];
    System.arraycopy(data, 0, data1, 0, 4);
    System.arraycopy(data, 4, data2, 0, 4);
    return ((long)(getInt(data1)) << 32) + (getInt(data2) & 0xFFFFFFFFL);
  }
  


  public static long getLong(byte[] data, int offset) {
    byte[] data1 = new byte[4];
    byte[] data2 = new byte[4];
    System.arraycopy(data, offset, data1, 0, 4);
    System.arraycopy(data, 4+offset, data2, 0, 4);
    return ((long)(getInt(data1)) << 32) + (getInt(data2) & 0xFFFFFFFFL);	
    
  }



  /** This method is primarily used for debugging purposes, and provides
      us with a representation of a byte as sequence of 1's and 0's that is
      of length=8.
      @return The String representation of a byte as 1's and 0's
  */
  public static String printByte(byte b) {
    char[] byteString=new char[8];
    short x = b;
    if ( (x & (0x0001)) !=0) {
      byteString[7] = '1';
    } else byteString[7] = '0';
    
    for (int i=0; i <7 ; i++) {
      if ( ((x >>= 1) & (0x0001)) !=0 ) {
	byteString[6-i] = '1';
      } else {
	byteString[6-i]= '0';
      }    
    }
    return (new String(byteString) ) ;
  }



  /** This method is primarily used for debugging purposes, and provides
      us with a representation of an integer as sequence of 1's and 0's that 
      is of length=32.
      @return The String representation of an integer as 1's and 0's
  */
  public static String printInt(int value) {
    byte[] byteValue = getBytes(value);
    String intString = "";
    for (int i=0; i <4; i++) {
      intString += printByte(byteValue[i]);
    }
    return intString;
  }

  
  /** This performs an inversion of the bits in an integer. There are no
      unary operators which do this in Java. I have implemented the NOT
      operation using an XOR operator */
  public static int  performNotOperation(int value) {
    int newVal = value ^ 0xFFFFFFFF;
    return newVal;
  }


  
  /** Returns the position (1 through 32) of the first "1"-bit in the binary
      representation of the supplied integer. This method returns a "0" if
      there are no 1-bits in the binary representation i.e. if the supplied 
      integer is zero.  */
  public static int getFirstOneBitPosition(int value) {
    int lowTrack = 1;
    int lowestVal= 0;
    int destVal = value;
    if (destVal == 0) {
      return lowestVal;
    }
    
    for (int i=0; i < 32; i++) {
      int andVal = destVal & lowTrack;
      if (andVal == 1) {
	lowestVal = (i+1);
	break;
      } /* end if */
      destVal = destVal >> 1;
    } /* end for */
    return lowestVal;
  }
  
  
  
  /** Returns the position (1 through 32) of the last "1"-bit in the binary
      representation of the supplied integer. This method returns a "0" if
      there are no 1-bits in the binary representation i.e. if the supplied 
      integer is zero.  */
  public static int getLastOneBitPosition(int value) { 
    int highTrack = 0x80000000;
    int highestVal= 0;
    int destVal = value;
    if (destVal == 0) {
      return highestVal;
    }
    
    for (int i=32; i > 0; i--) {
      int andVal = destVal & highTrack;
      if (andVal !=0 ) {
	highestVal = i;
	break;
      } /* end if */
      destVal = destVal << 1;
    } /* end for */
    return highestVal;
  }



  /** Returns the position (1 through 32) of the first "0"-bit in the binary
      representation of the supplied integer. This method returns a "0" if
      there are no 0-bits in the binary representation i.e. if the supplied 
      integer is 0xFFFF.  */
  public static int getFirstZeroBitPosition(int value) {
    int lowTrack = 1;
    int lowestVal= 0;
    int destVal = value;
    if (destVal == 0) {
      return (lowestVal+1);
    }
    
    for (int i=0; i < 32; i++) {
      int andVal = destVal & lowTrack;
      if (andVal == 0) {
	lowestVal = (i+1);
	break;
      } /* end if */
      destVal = destVal >> 1;
    } /* end for */
    return lowestVal;
  }
    

  
  public static void main(String[] args) {
    /*
      int[] address = {5, 31, 43, 63};
      byte[] addressBytes = new ByteUtilities(address).getAddressInBytes();
      
      for (int i=0; i <addressBytes.length/2; i++) {
      System.out.print(addressBytes[2*i]);
      System.out.println(addressBytes[2*i +1]);
      }
      }
    */
    //short x = 6000;
    System.out.println(Short.MAX_VALUE);
    System.out.println(ByteUtilities.getShort(ByteUtilities.getBytes(Short.MAX_VALUE)) );
    
    System.out.println(Integer.MAX_VALUE);
    System.out.println(ByteUtilities.getInt(ByteUtilities.getBytes(Integer.MAX_VALUE)) );
    
    System.out.println(Long.MAX_VALUE);
    System.out.println(ByteUtilities.getLong(ByteUtilities.getBytes(Long.MAX_VALUE)) );
    
    /*int x = 255;
      byte[] data = ByteUtilities.getBytes(x);
      for (int i =0; i < data.length; i++) {
      System.out.println(ByteUtilities.printByte(data[i]));
      }*/
    
  }
}




